// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SnapObjects.Data
{
    internal abstract class SqlStatementGenerator : ISqlStatementGenerator
    {
        protected ISqlStatementFactory _factory;
        protected ISqlGenerationHelper _helper;
        protected ITypeMapper _mapper;

        public SqlStatementGenerator(
            ISqlStatementFactory factory,
            ISqlGenerationHelper helper,
            ITypeMapper typeMapper)
        {
            _factory = factory;
            _helper = helper;
            _mapper = typeMapper;
        }

        protected virtual string GenerateParameterName(string name)
        {
            return _helper.GenerateParameterName(name);
        }

        public virtual SqlSyntaxDescription Create(SqlStatement statement)
        {
            var ignoreDelimit = false;

            if (statement is SqlSelectStatement smt)
            {
                ignoreDelimit = smt.IgnoreDelimitIdentifier;
            }

            var tokens = this.SpliceStatement(statement);

            var builder = new StringBuilder();

            var parameters =
                new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            SqlKeywords[] wrapBegin = {
                SqlKeywords.FROM,
                SqlKeywords.WHERE,
                SqlKeywords.GROUP,
                SqlKeywords.ORDER,
                SqlKeywords.HAVING,
                SqlKeywords.UNION,
                SqlKeywords.AND,
                SqlKeywords.OR,
                SqlKeywords.ON,
                SqlKeywords.INNER,
                SqlKeywords.LEFT,
                SqlKeywords.RIGHT,
                SqlKeywords.FULL };
            SqlKeywords[] wrapEnd = {
                SqlKeywords.SELECT,
                SqlKeywords.ALL,
                SqlKeywords.BY };

            SqlToken prevToken = null;
            SqlKeywords currKeyWork = null;

            for (var i = 0; i < tokens.Count; i++)
            {
                var token = tokens[i];

                switch (token.Type)
                {
                    case SqlTokenType.Keyword:
                    {
                        var key = (SqlKeywordToken)token;

                        currKeyWork = key.Keyword;

                        if (wrapBegin.Contains(key.Keyword))
                        {
                            builder.Append("\r\n");
                            builder.Append(key.Text);
                        }
                        else if (wrapEnd.Contains(key.Keyword))
                        {
                            builder.AppendLine(key.Text);
                        }
                        else
                        {
                            builder.Append(key.Text);
                        }
                    }
                    break;
                    case SqlTokenType.Parameter:
                    {
                        //string parameterName = _helper.GenerateParameterName(token.Text);
                        var parameterName = this.GenerateParameterName(token.Text);

                        if (!parameters.ContainsKey(token.Text))
                        {
                            parameters.Add(token.Text, parameterName);
                        }

                        builder.Append(parameterName);
                    }
                    break;
                    case SqlTokenType.Value:
                    {
                        var value = (SqlValueToken)token;

                        if (value.ClrType != null)
                        {
                            var mapping = _mapper.FindMapping(value.ClrType);

                            builder.Append(mapping.GenerateSqlLiteral(value.Value));
                        }
                        else
                        {
                            builder.Append("NULL");
                        }
                    }
                    break;
                    case SqlTokenType.Identifier:
                    {
                        var identifer = token.Text.Trim();

                        if (identifer.Length == 1 && !Char.IsLetter(identifer[0]))
                        {
                            builder.Append(identifer);
                        }
                        else if (identifer.StartsWith("\"") && identifer.EndsWith("\""))
                        {
                            builder.Append(identifer);
                        }
                        else
                        {
                            if (this.CheckKeyWork(currKeyWork))
                            {
                                var nextIndex = i;
                                var nextToken = this.GetNextNotSpace(tokens, ref nextIndex);

                                if (nextToken != null)
                                {
                                    if (!nextToken.IsCharacter(SqlCharacters.OpenParentheses)
                                        && prevToken != null
                                        && !_helper.IsDelimitIdentifier(prevToken.Text, nextToken.Text))
                                    {
                                        if (!ignoreDelimit)
                                        {
                                            identifer = _helper.DelimitIdentifier(identifer);
                                        }
                                    }
                                }
                                else
                                {
                                    if (!ignoreDelimit)
                                    {
                                        identifer = _helper.DelimitIdentifier(identifer);
                                    }
                                }
                            }

                            builder.Append(identifer);
                        }
                    }
                    break;
                    case SqlTokenType.Character:
                    {
                        var character = (SqlCharacterToken)token;
                        if (character.Character == SqlCharacters.Comma)
                        {
                            builder.AppendLine(character.Text);
                        }
                        else
                        {
                            builder.Append(token.Text);
                        }
                    }
                    break;
                    case SqlTokenType.Whitespace:
                    {
                        if (prevToken != null && prevToken.Type != SqlTokenType.Whitespace)
                        {
                            builder.Append(token.Text);
                        }
                    }
                    break;
                    case SqlTokenType.Statement:
                    {
                        var stmt = (SqlStatement)token;

                        var generator = _factory.GetSqlGenerator(stmt.StatementType);

                        var des = generator.Create(stmt);

                        builder.Append(des.SqlText);
                    }
                    break;
                    default:
                    {
                        builder.Append(token.Text);
                    }
                    break;
                }

                prevToken = token;
            }

            return new SqlSyntaxDescription(builder.ToString(), parameters);
        }

        private bool CheckKeyWork(SqlKeywords keywords)
        {
            if (keywords == SqlKeywords.COLLATE)
            {
                keywords = null;

                return false;
            }

            return true;
        }

        private SqlToken GetNextNotSpace(IReadOnlyList<SqlToken> tokens, ref int index)
        {
            var i = index + 1;

            if (i < tokens.Count)
            {
                if (tokens[i].Type == SqlTokenType.Whitespace)
                {
                    return this.GetNextNotSpace(tokens, ref i);
                }
                else
                {
                    index = i;

                    return tokens[i];
                }
            }

            return null;
        }

        protected virtual IEnumerable<SqlToken> SpliceClause(SqlClause clause)
        {
            var tokens = new List<SqlToken>();

            SqlToken prev = null;

            foreach (var token in clause)
            {
                if (token is SqlStatement statement)
                {
                    if (prev != null
                        && prev is SqlCharacterToken c
                        && c.Character == SqlCharacters.OpenParentheses)
                    {
                        tokens.AddRange(this.SpliceStatement(statement));
                    }
                    else
                    {
                        tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));
                        tokens.AddRange(this.SpliceStatement(statement));
                        tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));
                    }
                }
                else if (token != null)
                {
                    if (token.Type != SqlTokenType.Whitespace)
                    {
                        prev = token;
                    }

                    tokens.Add(token);
                }
            }

            return tokens;
        }

        protected virtual IReadOnlyList<SqlToken> SpliceStatement(SqlStatement statement)
        {
            var generator = (SqlStatementGenerator)_factory.GetSqlGenerator(statement.StatementType);

            return generator.SpliceStatement(statement);
        }
    }
}
